
<template>
    <div class="virtual-scroller" @scroll="handleScroll" ref="scroller">
        <div class="scroll-phantom" :style="{ height: totalHeight + 'px' }"></div>
        <div class="scroll-content" :style="{ transform: `translateY(${offset}px)` }">
            <div v-for="item in visibleData" :key="item.id" class="list-item" :style="{ height: itemSize + 'px' }">
                <slot :item="item"></slot>
            </div>
        </div>
    </div>
</template>
  
<script>
export default {
    props: {
        items: { type: Array, required: true },
        itemSize: { type: Number, default: 50 },
        buffer: { type: Number, default: 5 }  //通过预加载额外列表项避免快速滚动时出现空白区域
    },
    data() {
        return {
            startIndex: 0,
            endIndex: 0,
            offset: 0
        }
    },
    computed: {
        totalHeight() {
            //totalHeight=800
            return this.items.length * this.itemSize
        },
        visibleCount() {
            //假如视口高度就是80，visibleCount=1
            return Math.ceil(this.$refs.scroller?.clientHeight / this.itemSize) || 10
        },
        visibleData() {
            return this.items.slice(this.startIndex, this.endIndex)
        }
    },
    mounted() {
        this.updateVisibleRange()
        window.addEventListener('resize', this.handleResize)
    },
    beforeDestroy() {
        window.removeEventListener('resize', this.handleResize)
    },
    methods: {
        handleScroll() {
            requestAnimationFrame(() => {
                this.updateVisibleRange()
            })
        },
        handleResize() {
            this.updateVisibleRange()
        },
        updateVisibleRange() {
            //假如scrollTop = 20
            console.log(this.$refs.scroller?.scrollTop,'this.$refs.scroller?.scrollTop')
            const scrollTop = this.$refs.scroller?.scrollTop || 0
            this.startIndex = Math.max(
                0,
                Math.floor(scrollTop / this.itemSize) - this.buffer
            )
            this.endIndex = Math.min(
                this.items.length,
                this.startIndex + this.visibleCount + this.buffer * 2
            )
            this.offset = this.startIndex * this.itemSize
        }
    },
    watch: {
        items() {
            this.$nextTick(() => this.updateVisibleRange())
        }
    }
}
</script>
  
<style scoped>
.virtual-scroller {
    position: relative;
    overflow-y: auto;
    height: 100%;
}

.scroll-phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
}

.scroll-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}

.list-item {
    position: relative;
    box-sizing: border-box;
}
</style>
  